﻿
Imports osi.root.connector
Imports osi.root.formation
Imports osi.root.delegates
Imports osi.root.lock
Imports osi.root.constants
Imports osi.root.utils
Imports osi.root.utils.stopwatch
Imports osi.root.envs
Imports osi.root.constants.error_handle
Imports counter = osi.root.utils.counter
'Imports lock_t = osi.root.lock.lock(Of osi.root.lock.slimlock.simplelock)
Imports lock_t = osi.root.lock.monitorlock
Imports stack = osi.root.formation.fixed_stack_wrapper(Of osi.root.procedure.event_comb, osi.root.template._4096)

Partial Public Class event_comb
    Public Const end_step As Int32 = maxInt32
    Public Const not_started_step As Int32 = -1
    Public Const first_step As Int32 = 0
    Private Shared ReadOnly EVENTCOMB_SPINLOCK_WAIT_RATE As Int64
    Private ReadOnly ds() As _do(Of Boolean) = Nothing
    Private ReadOnly _callstack As String = Nothing
    Protected timeouting As void = Nothing
    Protected resetting As void = Nothing
    Private _end_result As ternary = ternary.unknown
    Private finishing As void = Nothing
    Private timeouted_event As stopwatch_event = Nothing
    Private _l As lock_t
    Private cb As event_comb = Nothing
    Private [step] As Int32 = not_started_step
    Private pends As UInt64 = 0

    <ThreadStatic()> Private Shared _current As stack
    Public Shared Property current As event_comb
        Get
            Dim c As stack = Nothing
            c = _current
            assert(Not c Is Nothing)
            Return c.back()
        End Get
        Protected Set(ByVal value As event_comb)
            Dim c As stack = Nothing
            c = _current
            If c Is Nothing Then
                c = New stack()
                _current = c
            End If
            If value Is Nothing Then
                c.pop()
            Else
                c.push(value)
            End If
        End Set
    End Property

    Shared Sub New()
        If event_comb_trace Then
            EVENTCOMB_SPINLOCK_WAIT_RATE = counter.register_rate_and_last_rate("EVENTCOMB_SPINLOCK_WAIT_RATE")
        End If
    End Sub

    Protected Sub reenterable_locked(ByVal d As void)
        If event_comb_trace Then
            Dim n As Int64 = 0
            n = Now.milliseconds()
            _l.reenterable_locked(d)
            If Now.milliseconds() - n > half_timeslice_length_ms Then
                raise_error(error_type.performance,
                            callstack(), ":", [step],
                            " is using ", Now.milliseconds() - n, "ms to wait for another thread to finish")
            End If
        Else
            _l.reenterable_locked(d)
        End If
    End Sub

    Public Sub reset()
        reenterable_locked(Sub()
                               assert_goto_not_started()
                               _end_result = ternary.unknown
                               clear_pends()
                               cb = Nothing
                               begin_ticks() = npos
                               end_ticks() = npos
                               timeouted_event = Nothing
                               void_(resetting)
                           End Sub)
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Protected Sub New(ByVal d() As _do(Of Boolean), ByVal additionalJump As Int64)
        copy(ds, d)
        If isdebugmode() OrElse event_comb_trace Then
            _callstack = callingcode(additionalJump + 1)
        Else
            _callstack = "##NOT_TRACE##"
        End If
        reset()
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Sub New(ByVal ParamArray d() As _do(Of Boolean))
        Me.New(d, 1)
    End Sub

    Protected Function callstack() As String
        Return _callstack
    End Function

    Public Function end_result_raw() As ternary
        Return _end_result
    End Function

    Public Function end_result() As Boolean
        assert(end_result_raw().notunknown())
        Return end_result_raw().true_()
    End Function

    Public Function pending() As Boolean
        Return pends > 0
    End Function

    Public Function not_pending() As Boolean
        Return pends = 0
    End Function

    Private Sub assert_in_lock()
        If isdebugmode() Then
            assert(_l.held_in_thread())
        End If
    End Sub

    Protected Sub inc_pends()
        assert_in_lock()
        pends += 1
    End Sub

    Protected Sub dec_pends()
        assert_in_lock()
        assert(pends > 0)
        pends -= 1
    End Sub

    Protected Sub mark_as_failed()
        _end_result = False
    End Sub

    Private Sub clear_pends()
        assert_in_lock()
        pends = 0
    End Sub

    Private _begin_ticks As Int64 = npos
    Public Property begin_ticks() As Int64
        Get
            Return _begin_ticks
        End Get
        Private Set(ByVal value As Int64)
            assert(_begin_ticks = npos OrElse value = npos)
            _begin_ticks = value
        End Set
    End Property

    Private _end_ticks As Int64 = npos
    Public Property end_ticks() As Int64
        Get
            Return _end_ticks
        End Get
        Private Set(ByVal value As Int64)
            assert(_end_ticks = npos OrElse value = npos)
            _end_ticks = value
        End Set
    End Property

    Protected Sub _do()
        assert_in_lock()
        assert(end_result_raw().unknown_() OrElse ending())
        assert(not_pending())

        If [end]() Then
            'the timeout has been called before the queued item in thread pool
            Return
        End If

        If not_started() Then
            If isemptyarray(ds) Then
                assert_goto_end()
            Else
                assert_goto_begin()
            End If
            begin_ticks() = Now.Ticks()
        End If

        Dim laststep As Int32 = 0
        While working() AndAlso not_pending()
            Dim rtn As Boolean = False
            laststep = [step]
            current() = Me
            Try
                rtn = do_(ds([step]), False)
            Finally
                current() = Nothing
            End Try
            If rtn = False Then
                If isdebugmode() Then
                    raise_error(error_type.warning,
                                "event ", callstack(), ":<step>", laststep, " failed")
                End If
                assert_goto_end()
                clear_pends()
            End If
            If in_end_step() Then
                _end_result = rtn
            End If
        End While

        If ending() Then
            end_ticks() = Now.Ticks()
            void_(finishing)
            [resume](cb)
        End If
    End Sub

    Private Shared Sub [resume](ByVal cb As event_comb)
        If Not cb Is Nothing Then
            cb.reenterable_locked(Sub()
                                      If cb.pending() Then
                                          cb.dec_pends()
                                          If cb.not_pending() Then
                                              cb._do()
                                          End If
                                      End If
                                  End Sub)
        End If
    End Sub

    Friend Sub [do]()
        reenterable_locked(AddressOf _do)
    End Sub

    Private Function cancel_timeout_event() As Boolean
        assert_in_lock()
        If timeouted_event Is Nothing Then
            Return False
        Else
            timeouted_event.cancel()
            timeouted_event = Nothing
            Return True
        End If
    End Function

    Friend Sub set_timeout(ByVal timeout_ms As Int64)
        reenterable_locked(Sub()
                               If timeout_ms < 0 Then
                                   cancel_timeout_event()
                               Else
                                   Dim wp As weak_pointer(Of event_comb) = Nothing
                                   wp = New weak_pointer(Of event_comb)(Me)
                                   timeouted_event = stopwatch.push(timeout_ms,
                                                                    Sub()
                                                                        'may be canceled between
                                                                        'stopwatch canceled test and do
                                                                        Dim ec As event_comb = Nothing
                                                                        ec = (+wp)
                                                                        If Not ec Is Nothing Then
                                                                            ec.timeout()
                                                                        End If
                                                                    End Sub)
                                   assert(Not timeouted_event Is Nothing)
                                   finishing = Sub() assert(cancel_timeout_event())
                               End If
                           End Sub)
    End Sub

    Private Sub timeout()
        reenterable_locked(Sub()
                               If Not [end]() AndAlso
                                  object_compare(timeouted_event, stopwatch_event.current()) = 0 Then
                                   void_(timeouting)
                                   If event_comb_trace Then
                                       raise_error(error_type.warning, "event " + callstack() + " timeout")
                                   End If
                                   assert_goto_end()
                                   mark_as_failed()
                                   clear_pends()
                                   _do()
                                   assert([end]())
                               End If
                           End Sub)
    End Sub

    Protected Overrides Sub Finalize()
        reenterable_locked(Sub() cancel_timeout_event())
        MyBase.Finalize()
    End Sub
End Class
